package com.fastjrun.xxl.job.api.service.impl;

import com.fastjrun.common.ServiceException;
import com.fastjrun.service.BaseService;
import com.fastjrun.xxl.job.api.common.CodeMsgConstants;
import com.fastjrun.xxl.job.api.common.ExecutorRouteStrategyEnum;
import com.fastjrun.xxl.job.api.common.MisfireStrategyEnum;
import com.fastjrun.xxl.job.api.common.ScheduleTypeEnum;
import com.fastjrun.xxl.job.api.dao.XxlJobGroupDao;
import com.fastjrun.xxl.job.api.dao.XxlJobInfoDao;
import com.fastjrun.xxl.job.api.dao.XxlJobLogDao;
import com.fastjrun.xxl.job.api.dao.XxlJobLogglueDao;
import com.fastjrun.xxl.job.api.dto.XxlJobInfoDTO;
import com.fastjrun.xxl.job.api.entity.XxlJobGroup;
import com.fastjrun.xxl.job.api.entity.XxlJobInfo;
import com.fastjrun.xxl.job.api.entity.XxlJobLogExample;
import com.fastjrun.xxl.job.api.entity.XxlJobLogglueExample;
import com.fastjrun.xxl.job.api.service.OpenApiControllerService;
import com.fastjrun.xxl.job.api.vo.XxlJobInfoVO;
import com.fastjrun.xxl.job.api.helper.CronExpression;
import com.fastjrun.xxl.job.api.helper.DTOToEntifyConverter;
import com.fastjrun.xxl.job.api.helper.EntityToVOConverter;
import com.fastjrun.xxl.job.api.helper.JobScheduleHelper;
import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
import com.xxl.job.core.glue.GlueTypeEnum;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Date;
import java.util.Locale;

/** @author javafun */
@Service("openApiControllerService")
public class OpenApiControllerServiceImpl extends BaseService implements OpenApiControllerService {
  @Resource XxlJobInfoDao xxlJobInfoDao;

  @Resource XxlJobGroupDao xxlJobGroupDao;

  @Resource XxlJobLogglueDao xxlJobLogglueDao;

  @Resource XxlJobLogDao xxlJobLogDao;

  @Override
  public Integer add(XxlJobInfoDTO requestBody) {
    validate(requestBody);
    XxlJobInfo xxlJobInfo = DTOToEntifyConverter.convert(requestBody);

    xxlJobInfo.setTriggerStatus(0);
    xxlJobInfo.setTriggerLastTime(0L);
    xxlJobInfo.setTriggerNextTime(0L);

    xxlJobInfo.setGlueUpdatetime(new Date());
    xxlJobInfo.setUpdateTime(new Date());
    xxlJobInfo.setAddTime(new Date());

    // 返回自增ID
    xxlJobInfoDao.insert(xxlJobInfo);
    return xxlJobInfo.getId();
  }

  private void validate(XxlJobInfoDTO requestBody) {
    // valid base
    XxlJobGroup group = xxlJobGroupDao.selectByPrimaryKey(requestBody.getJobGroup());
    if (group == null) {
      throw new ServiceException(
          CodeMsgConstants.NEEDED_40401,
          serviceMessageSource.getMessage(
              CodeMsgConstants.NEEDED_40401,
              new String[] {"执行器:" + String.valueOf(requestBody.getJobGroup())},
              Locale.getDefault()));
    }
    if (requestBody.getJobDesc() == null || requestBody.getJobDesc().trim().length() == 0) {
      throw new ServiceException(
          CodeMsgConstants.NEEDED_40401,
          serviceMessageSource.getMessage(
              CodeMsgConstants.NEEDED_40401, new String[] {"任务描述"}, Locale.getDefault()));
    }
    if (requestBody.getAuthor() == null || requestBody.getAuthor().trim().length() == 0) {
      throw new ServiceException(
          CodeMsgConstants.NEEDED_40401,
          serviceMessageSource.getMessage(
              CodeMsgConstants.NEEDED_40401, new String[] {"负责人"}, Locale.getDefault()));
    }
    // valid trigger
    ScheduleTypeEnum scheduleTypeEnum = ScheduleTypeEnum.match(requestBody.getScheduleType(), null);
    if (scheduleTypeEnum == null) {
      throw new ServiceException(
          CodeMsgConstants.NEEDED_40401,
          serviceMessageSource.getMessage(
              CodeMsgConstants.NEEDED_40401, new String[] {"调度类型"}, Locale.getDefault()));
    }
    if (scheduleTypeEnum == ScheduleTypeEnum.CRON) {
      if (requestBody.getScheduleConf() == null
          || !CronExpression.isValidExpression(requestBody.getScheduleConf())) {
        throw new ServiceException(
            CodeMsgConstants.NEEDED_40401,
            serviceMessageSource.getMessage(
                CodeMsgConstants.INPUT_NOT_VALID_40402,
                new String[] {"Cron"},
                Locale.getDefault()));
      }
    } else if (scheduleTypeEnum
        == ScheduleTypeEnum.FIX_RATE /* || scheduleTypeEnum == ScheduleTypeEnum.FIX_DELAY*/) {
      if (requestBody.getScheduleConf() == null) {
        throw new ServiceException(
            CodeMsgConstants.INPUT_NOT_VALID_40402,
            serviceMessageSource.getMessage(
                CodeMsgConstants.INPUT_NOT_VALID_40402,
                new String[] {"调度类型"},
                Locale.getDefault()));
      }
      try {
        int fixSecond = Integer.valueOf(requestBody.getScheduleConf());
        if (fixSecond < 1) {
          throw new ServiceException(
              CodeMsgConstants.INPUT_NOT_VALID_40402,
              serviceMessageSource.getMessage(
                  CodeMsgConstants.INPUT_NOT_VALID_40402,
                  new String[] {"调度类型"},
                  Locale.getDefault()));
        }
      } catch (Exception e) {
        log.error(e.getMessage(), e);
        throw new ServiceException(
            CodeMsgConstants.INPUT_NOT_VALID_40402,
            serviceMessageSource.getMessage(
                CodeMsgConstants.INPUT_NOT_VALID_40402,
                new String[] {"调度类型"},
                Locale.getDefault()));
      }
    }
    // valid job
    if (GlueTypeEnum.match(requestBody.getGlueType()) == null) {
      throw new ServiceException(
          CodeMsgConstants.NEEDED_40401,
          serviceMessageSource.getMessage(
              CodeMsgConstants.NEEDED_40401, new String[] {"运行模式"}, Locale.getDefault()));
    }
    if (GlueTypeEnum.BEAN == GlueTypeEnum.match(requestBody.getGlueType())
        && (requestBody.getExecutorHandler() == null
            || requestBody.getExecutorHandler().trim().length() == 0)) {
      throw new ServiceException(
          CodeMsgConstants.NEEDED_40401,
          serviceMessageSource.getMessage(
              CodeMsgConstants.NEEDED_40401,
              new String[] {"JobHandler"},
              Locale.getDefault()));
    }
    // 》fix "\r" in shell
    if (GlueTypeEnum.GLUE_SHELL == GlueTypeEnum.match(requestBody.getGlueType())
        && requestBody.getGlueSource() != null) {
      requestBody.setGlueSource(requestBody.getGlueSource().replaceAll("\r", ""));
    }

    // valid advanced
    if (ExecutorRouteStrategyEnum.match(requestBody.getExecutorRouteStrategy(), null) == null) {
      throw new ServiceException(
          CodeMsgConstants.NEEDED_40401,
          serviceMessageSource.getMessage(
              CodeMsgConstants.NEEDED_40401, new String[] {"路由策略"}, Locale.getDefault()));
    }
    if (MisfireStrategyEnum.match(requestBody.getMisfireStrategy(), null) == null) {
      throw new ServiceException(
          CodeMsgConstants.NEEDED_40401,
          serviceMessageSource.getMessage(
              CodeMsgConstants.NEEDED_40401, new String[] {"调度过期策略"}, Locale.getDefault()));
    }
    if (ExecutorBlockStrategyEnum.match(requestBody.getExecutorBlockStrategy(), null) == null) {
      throw new ServiceException(
          CodeMsgConstants.NEEDED_40401,
          serviceMessageSource.getMessage(
              CodeMsgConstants.NEEDED_40401, new String[] {"阻塞处理策略"}, Locale.getDefault()));
    }

    // 》ChildJobId valid
    if (requestBody.getChildJobId() != null && requestBody.getChildJobId().trim().length() > 0) {
      String[] childJobIds = requestBody.getChildJobId().split(",");
      for (String childJobIdItem : childJobIds) {
        if (childJobIdItem != null
            && childJobIdItem.trim().length() > 0
            && isNumeric(childJobIdItem)) {
          XxlJobInfo childJobInfo =
              xxlJobInfoDao.selectByPrimaryKey(Integer.parseInt(childJobIdItem));
          if (childJobInfo == null) {
            throw new ServiceException(
                CodeMsgConstants.NEEDED_40401,
                serviceMessageSource.getMessage(
                    CodeMsgConstants.NEEDED_40401,
                    new String[] {"子任务ID:" + childJobIdItem},
                    Locale.getDefault()));
          }
        } else {
          throw new ServiceException(
              CodeMsgConstants.NEEDED_40401,
              serviceMessageSource.getMessage(
                  CodeMsgConstants.NEEDED_40401, new String[] {"子任务ID"}, Locale.getDefault()));
        }
      }

      // join , avoid "xxx,,"
      String temp = "";
      for (String item : childJobIds) {
        temp += item + ",";
      }
      temp = temp.substring(0, temp.length() - 1);
      requestBody.setChildJobId(temp);
    }
  }

  @Override
  public void update(XxlJobInfoDTO requestBody) {
    validate(requestBody);
    XxlJobInfo exists_jobInfo = xxlJobInfoDao.selectByPrimaryKey(requestBody.getId());
    if (exists_jobInfo == null) {
      throw new ServiceException(
              CodeMsgConstants.NOT_EXISTED_40404,
              serviceMessageSource.getMessage(
                      CodeMsgConstants.NOT_EXISTED_40404,
                      new String[] {"任务"+requestBody.getId()},
                      Locale.getDefault()));
    }
    XxlJobInfo xxlJobInfo = DTOToEntifyConverter.convert(requestBody);
    // next trigger time (5s后生效，避开预读周期)
    long nextTriggerTime = exists_jobInfo.getTriggerNextTime();
    boolean scheduleDataNotChanged =
        requestBody.getScheduleType().equals(exists_jobInfo.getScheduleType())
            && requestBody.getScheduleConf().equals(exists_jobInfo.getScheduleConf());
    if (exists_jobInfo.getTriggerStatus() == 1 && !scheduleDataNotChanged) {
      try {
        Date nextValidTime =
            JobScheduleHelper.generateNextValidTime(
                xxlJobInfo, new Date(System.currentTimeMillis() + JobScheduleHelper.PRE_READ_MS));
        if (nextValidTime == null) {
          throw new ServiceException(
              CodeMsgConstants.INPUT_NOT_VALID_40402,
              serviceMessageSource.getMessage(
                  CodeMsgConstants.INPUT_NOT_VALID_40402,
                  new String[] {"调度类型"},
                  Locale.getDefault()));
        }
        nextTriggerTime = nextValidTime.getTime();
      } catch (Exception e) {
        log.error(e.getMessage(), e);
        throw new ServiceException(
            CodeMsgConstants.INPUT_NOT_VALID_40402,
            serviceMessageSource.getMessage(
                CodeMsgConstants.INPUT_NOT_VALID_40402,
                new String[] {"调度类型"},
                Locale.getDefault()));
      }
    }
    xxlJobInfo.setTriggerStatus(exists_jobInfo.getTriggerStatus());
    xxlJobInfo.setTriggerLastTime(exists_jobInfo.getTriggerLastTime());

    xxlJobInfo.setGlueUpdatetime(exists_jobInfo.getGlueUpdatetime());
    xxlJobInfo.setAddTime(exists_jobInfo.getAddTime());

    xxlJobInfo.setTriggerNextTime(nextTriggerTime);
    xxlJobInfo.setUpdateTime(new Date());

    xxlJobInfoDao.updateByPrimaryKey(xxlJobInfo);
  }

  @Override
  public void delete(Integer id) {
    XxlJobInfo xxlJobInfo = xxlJobInfoDao.selectByPrimaryKey(id);
    if (xxlJobInfo == null) {
      throw new ServiceException(
              CodeMsgConstants.NOT_EXISTED_40404,
              serviceMessageSource.getMessage(
                      CodeMsgConstants.NOT_EXISTED_40404,
                      new String[] {"任务"+id},
                      Locale.getDefault()));
    }

    xxlJobInfoDao.deleteByPrimaryKey(id);
    XxlJobLogExample xxlJobLogExample = new XxlJobLogExample();
    xxlJobLogExample.createCriteria().andJobIdEqualTo(id);
    xxlJobLogDao.deleteByExample(xxlJobLogExample);
    XxlJobLogglueExample xxlJobLogglueExample = new XxlJobLogglueExample();
    xxlJobLogglueExample.createCriteria().andJobIdEqualTo(id);
    xxlJobLogglueDao.deleteByExample(xxlJobLogglueExample);
  }

  @Override
  public void start(Integer id) {
    XxlJobInfo xxlJobInfo = xxlJobInfoDao.selectByPrimaryKey(id);
    if (xxlJobInfo == null) {
      throw new ServiceException(
              CodeMsgConstants.NOT_EXISTED_40404,
              serviceMessageSource.getMessage(
                      CodeMsgConstants.NOT_EXISTED_40404,
                      new String[] {"任务"+id},
                      Locale.getDefault()));
    }
    // valid
    ScheduleTypeEnum scheduleTypeEnum =
        ScheduleTypeEnum.match(xxlJobInfo.getScheduleType(), ScheduleTypeEnum.NONE);
    if (ScheduleTypeEnum.NONE == scheduleTypeEnum) {
      throw new ServiceException(
          CodeMsgConstants.NOT_ALLOWED_START_40403,
          serviceMessageSource.getMessage(
              CodeMsgConstants.NOT_ALLOWED_START_40403, null, Locale.getDefault()));
    }
    long nextTriggerTime;

    try {
      Date nextValidTime =
          JobScheduleHelper.generateNextValidTime(
              xxlJobInfo, new Date(System.currentTimeMillis() + JobScheduleHelper.PRE_READ_MS));
      if (nextValidTime == null) {
        throw new ServiceException(
            CodeMsgConstants.INPUT_NOT_VALID_40402,
            serviceMessageSource.getMessage(
                CodeMsgConstants.INPUT_NOT_VALID_40402,
                new String[] {"调度类型"},
                Locale.getDefault()));
      }
      nextTriggerTime = nextValidTime.getTime();
    } catch (Exception e) {
      log.error(e.getMessage(), e);
      throw new ServiceException(
          CodeMsgConstants.INPUT_NOT_VALID_40402,
          serviceMessageSource.getMessage(
              CodeMsgConstants.INPUT_NOT_VALID_40402, new String[] {"调度类型"}, Locale.getDefault()));
    }
    xxlJobInfo.setTriggerStatus(1);
    xxlJobInfo.setTriggerLastTime(0L);
    xxlJobInfo.setTriggerNextTime(nextTriggerTime);

    xxlJobInfo.setUpdateTime(new Date());

    xxlJobInfoDao.updateByPrimaryKey(xxlJobInfo);
  }

  @Override
  public void stop(Integer id) {
    XxlJobInfo xxlJobInfo = xxlJobInfoDao.selectByPrimaryKey(id);
    if (xxlJobInfo == null) {
      throw new ServiceException(
              CodeMsgConstants.NOT_EXISTED_40404,
              serviceMessageSource.getMessage(
                      CodeMsgConstants.NOT_EXISTED_40404,
                      new String[] {"任务"+id},
                      Locale.getDefault()));
    }

    xxlJobInfo.setTriggerStatus(0);
    xxlJobInfo.setTriggerLastTime(0L);
    xxlJobInfo.setTriggerNextTime(0L);

    xxlJobInfo.setUpdateTime(new Date());

    xxlJobInfoDao.updateByPrimaryKey(xxlJobInfo);
  }

  @Override
  public XxlJobInfoVO detail(Integer id) {
    return EntityToVOConverter.convert(xxlJobInfoDao.selectByPrimaryKey(id));
  }

  private boolean isNumeric(String str) {
    try {
      int result = Integer.valueOf(str);
      return true;
    } catch (NumberFormatException e) {
      return false;
    }
  }
}
